/**
 * 
 */
package edu.arizona.bio5.onto.decomposition;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLLogicalAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.modularity.OntologySegmenter;

import uk.ac.manchester.cs.graph.Edge;
import uk.ac.manchester.cs.graph.alg.TransitiveReductionAlgorithm;
import uk.ac.manchester.cs.graph.alg.TransitiveReductionAlgorithm.ReductionHandler;
import uk.ac.manchester.cs.owlapi.modularity.ModuleType;
import uk.ac.manchester.cs.owlapi.modularity.SyntacticLocalityModuleExtractor;
import uk.ac.manchester.cs.util.SetUtils;

/**
 * @author Pavel Klinov
 *
 * pklinov@cs.man.ac.uk, pklinov@clarkparsia.com
 * 
 * Mar 5, 2011
 */
public class ChiaraDecompositionAlgorithm implements ADAlgorithm {

	private ModuleType mModType = null;
	
	public ChiaraDecompositionAlgorithm() {
		mModType = ModuleType.BOT;
	}
	
	public ChiaraDecompositionAlgorithm(ModuleType modType) {		
		mModType = modType;
	}
	
	public AtomicDecomposition decompose(OWLOntologyManager manager, OWLOntology ontology, OntologySegmenter segmenter, Set<OWLLogicalAxiom> toDoAxioms) {
		final AtomicDecomposition ad = new AtomicDecomposition(ontology.getOntologyID().getOntologyIRI().toURI(), mModType);
		Map<Module, OWLAxiom> modAxiomMap = new HashMap<Module, OWLAxiom>(ontology.getLogicalAxiomCount() / 5);
		Set<OWLAxiom> minusTautologies = segmenter.extract(ontology.getSignature()); 
		Set<OWLAxiom> tautologies = SetUtils.difference(toDoAxioms, minusTautologies);
		
		//System.out.println(toDoAxioms.size() + " axioms to be partitioned");
		
		//Ignore syntactic tautologies - they are never a part of any module	
		toDoAxioms.retainAll(minusTautologies);
		
		System.out.println(toDoAxioms.size() + " axioms after removing syntactic tautologies");
		
		//Eliminate axioms which are not local w.r.t. the empty signature. 
		Set<OWLAxiom> globalAxioms = segmenter.extract(new HashSet<OWLEntity>()); 
		toDoAxioms.removeAll(globalAxioms);

		System.out.println(toDoAxioms.size() + " axioms after removing global axioms");
		//Compute modules for axioms
		for (OWLAxiom axiom : toDoAxioms) {
			//Modules for axioms are compact and genuine
			Module module = new Module(segmenter.extract( axiom.getSignature() ));
			//Check if the module is the same as one of the previously generated
			OWLAxiom sameModAxiom = modAxiomMap.get( module );
			
			if (sameModAxiom != null) {
				//The current axiom goes into the atom for the previous axiom
				Atom prevAtom = ad.getAtomForAxiom( sameModAxiom );
				
				ad.addAxiomToAtom(axiom, (MemAtom)prevAtom);
				
			} else {
				//The current axiom generated a new module, update the map and create a new atom
				Set<OWLAxiom> atomSet = new HashSet<OWLAxiom>();
				
				atomSet.add( axiom );
				ad.addAtom( atomSet );
				modAxiomMap.put( module, axiom );
			}
		}
		//The rest is to build the partial order
		//System.out.println("Building partial order, total size of the module-to-axiom map: " + total);
		
		for (Module module : modAxiomMap.keySet()) {
			
			OWLAxiom aAxiom = modAxiomMap.get( module );
			
			for (OWLAxiom bAxiom : module.getAxioms()) {
				
				if (aAxiom == bAxiom) continue;
				//Which atom bAxiom generates?
				Atom bAtom = ad.getAtomForAxiom( bAxiom );
				Atom aAtom = ad.getAtomForAxiom( aAxiom );
				
				if (bAtom != null && aAtom != bAtom) ad.addDependency( aAtom, bAtom );
			}
		}
		//System.out.println("Removing redundant edges...");
		//Remove redundant edges
		new TransitiveReductionAlgorithm<Atom, Object>(new ReductionHandler<Atom, Object>() {

			@Override
			public void onEdgeRemove(Edge<Atom, Object> edge) {
				ad.removeDependency(edge.getStart(), edge.getEnd());
			}})
			.compute(ad.getGraph());
		//Adding global axioms and syntactic tautologies
		ad.setGlobalAxioms(globalAxioms);
		ad.setTautologies(tautologies);
		//Finally, add entity annotations to atoms
		addAnnotations(ad, ontology);
		
		//System.out.println("Decomposed!");
		
		return ad;
	}
	
	/* 
	 */
	@Override
	public AtomicDecomposition decompose(OWLOntologyManager manager, OWLOntology ontology) {
		return decompose(manager, ontology, new SyntacticLocalityModuleExtractor(manager, ontology, mModType), ontology.getLogicalAxioms());
	}

	private void addAnnotations(AtomicDecomposition ad, OWLOntology ontology) {
		for (Atom atom : ad.getAtoms()) {
			//Entity annotations
			for (OWLEntity term : atom.getSignature()) {
				for (OWLAnnotationAssertionAxiom annAxiom : ontology.getAnnotationAssertionAxioms(term.getIRI())) {
					atom.addNonLogicalAxiom(annAxiom);
				}
				
				for (OWLDeclarationAxiom decl : ontology.getDeclarationAxioms(term)) {
					atom.addNonLogicalAxiom(decl);
				}
			}
		}
	}
}